package com.bstek.uflo.designer.security.manager.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.bstek.dorado.util.Assert;
import com.bstek.uflo.command.CommandService;
import com.bstek.uflo.designer.command.QueryListCommand;
import com.bstek.uflo.designer.command.QueryUniqueResultCommand;
import com.bstek.uflo.designer.convert.ConvertService;
import com.bstek.uflo.designer.model.Mapping;
import com.bstek.uflo.designer.model.edge.Connection;
import com.bstek.uflo.designer.model.node.Node;
import com.bstek.uflo.designer.model.process.Process;
import com.bstek.uflo.designer.security.AuthorizeAttribute;
import com.bstek.uflo.designer.security.attribute.ProcessAuthorizeAttribute;
import com.bstek.uflo.designer.security.command.UpdateNodeEntryCommand;
import com.bstek.uflo.designer.security.manager.SecurityManager;
import com.bstek.uflo.designer.security.model.AuthorityType;
import com.bstek.uflo.designer.security.model.NodeAttribute;
import com.bstek.uflo.designer.security.model.NodeEntry;
import com.bstek.uflo.designer.security.model.ProcessAttribute;
import com.bstek.uflo.designer.security.model.ProcessEntry;
import com.bstek.uflo.designer.security.model.ProcessEntryAssignee;
import com.bstek.uflo.model.ProcessDefinition;
import com.bstek.uflo.query.ProcessQuery;
import com.bstek.uflo.service.ProcessService;

/**
 * @author matt.yao@bstek.com
 * @since 1.0
 */
@Service(SecurityManager.BEAN_ID)
public class SecurityServiceManager implements SecurityManager, ApplicationContextAware {

	@Autowired
	@Qualifier(ProcessService.BEAN_ID)
	private ProcessService processService;

	@Autowired
	@Qualifier(ConvertService.BEAN_ID)
	private ConvertService convertService;

	@Autowired
	@Qualifier(CommandService.BEAN_ID)
	private CommandService commandService;

	private Collection<AuthorizeAttribute> authorizeAttributeColl;

	public List<ProcessDefinition> findDbProcess() throws Exception {
		ProcessQuery query = processService.createProcessQuery();
		query.addOrderDesc("createDate");
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<NodeEntry> findDbNodeEntry(String processEntryId) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(NodeEntry.class);
		dc.add(Restrictions.eq("processEntryId", processEntryId));
		return (List<NodeEntry>) commandService.executeCommand(new QueryListCommand(dc));
	}

	public NodeEntry findDbNodeEntryByNodeName(String processEntryId, String nodeName) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(NodeEntry.class);
		dc.add(Restrictions.eq("processEntryId", processEntryId));
		dc.add(Restrictions.eq("name", nodeName));
		return commandService.executeCommand(new QueryUniqueResultCommand<NodeEntry>(dc));
	}

	private NodeEntry findNodeEntryByNodeName(List<NodeEntry> dbNodeEntryList, String name) throws Exception {
		Assert.notNull(name);
		for (NodeEntry nodeEntry : dbNodeEntryList) {
			if (nodeEntry.getName().equals(name)) {
				return nodeEntry;
			}
		}
		return null;
	}

	public List<NodeEntry> findNodeEntryByProcessId(String processEntryId, long processId) throws Exception {
		String ufloXmlData = convertService.findProcessDefinitionXml(processId);
		Process process = convertService.ufloToModel(ufloXmlData);
		List<NodeEntry> resultNodeEntryList = new ArrayList<NodeEntry>();
		List<NodeEntry> dbNodeEntryList = this.findDbNodeEntry(processEntryId);
		NodeEntry nodeEntry = null;
		List<Node> nodeList = process.getNodes();
		for (Node node : nodeList) {
			NodeEntry dbNodeEntry = this.findNodeEntryByNodeName(dbNodeEntryList, node.getName());
			if (dbNodeEntry == null) {
				nodeEntry = new NodeEntry(UUID.randomUUID().toString(), node.getName(), node.getClass().getSimpleName(), true, true);
			} else {
				nodeEntry = new NodeEntry(dbNodeEntry.getId(), dbNodeEntry.getName(), node.getClass().getSimpleName(), dbNodeEntry.isRemovable(), dbNodeEntry.isModifiable());
				nodeEntry.setPersistence(true);
			}
			resultNodeEntryList.add(nodeEntry);
		}
		List<Connection> connectionList = process.getConnections();
		for (Connection connection : connectionList) {
			if (!StringUtils.hasText(connection.getName())) {
				connection.setName("to " + connection.getToNodeName());
			}
			NodeEntry dbNodeEntry = this.findNodeEntryByNodeName(dbNodeEntryList, connection.getName());
			if (dbNodeEntry == null) {
				nodeEntry = new NodeEntry(UUID.randomUUID().toString(), connection.getName(), connection.getClass().getSimpleName(), true, true);
			} else {
				nodeEntry = new NodeEntry(dbNodeEntry.getId(), dbNodeEntry.getName(), connection.getClass().getSimpleName(), dbNodeEntry.isRemovable(), dbNodeEntry.isModifiable());
				nodeEntry.setPersistence(true);
			}
			resultNodeEntryList.add(nodeEntry);
		}
		return resultNodeEntryList;
	}

	public List<NodeAttribute> findNodeAttributeByNodeType(String nodeEntryId, String nodeType) throws Exception {
		List<NodeAttribute> nodeAttributeList = new ArrayList<NodeAttribute>();
		NodeAttribute nodeAttribute = null;
		for (AuthorizeAttribute authorizeAttribute : authorizeAttributeColl) {
			if (authorizeAttribute.support(nodeType.toLowerCase())) {
				List<Mapping> mappingList = authorizeAttribute.getAttributes();
				if (mappingList != null) {
					List<NodeAttribute> dbNodeAttribute = this.findDbNodeAttribute(nodeEntryId);
					for (Mapping mapping : mappingList) {
						nodeAttribute = this.buildNodeAttribute(mapping, dbNodeAttribute);
						nodeAttributeList.add(nodeAttribute);
					}
				}
			}
		}
		return nodeAttributeList;
	}

	public List<ProcessAttribute> findProcessAttribute(String processEntryId) throws Exception {
		List<ProcessAttribute> processAttributeList = new ArrayList<ProcessAttribute>();
		ProcessAttribute processAttribute = null;
		for (AuthorizeAttribute authorizeAttribute : authorizeAttributeColl) {
			if (authorizeAttribute instanceof ProcessAuthorizeAttribute) {
				List<Mapping> mappingList = authorizeAttribute.getAttributes();
				if (mappingList != null) {
					List<ProcessAttribute> dbProcessAttribute = this.findDbProcessAttribute(processEntryId);
					for (Mapping mapping : mappingList) {
						processAttribute = this.buildProcessAttribute(mapping, dbProcessAttribute);
						processAttributeList.add(processAttribute);
					}
				}
			}
		}
		return processAttributeList;
	}

	@SuppressWarnings("unchecked")
	public List<ProcessAttribute> findDbProcessAttribute(String processEntryId) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(ProcessAttribute.class);
		dc.add(Restrictions.eq("processEntryId", processEntryId));
		return (List<ProcessAttribute>) commandService.executeCommand(new QueryListCommand(dc));
	}

	@SuppressWarnings("unchecked")
	public List<NodeAttribute> findDbNodeAttribute(String nodeEntryId) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(NodeAttribute.class);
		dc.add(Restrictions.eq("nodeEntryId", nodeEntryId));
		return (List<NodeAttribute>) commandService.executeCommand(new QueryListCommand(dc));
	}

	private ProcessAttribute buildProcessAttribute(Mapping mapping, List<ProcessAttribute> dbProcessAttribute) {
		if (dbProcessAttribute != null) {
			for (ProcessAttribute pa : dbProcessAttribute) {
				if (pa.getName().equals(mapping.getKey())) {
					return pa;
				}
			}
		}
		return new ProcessAttribute(UUID.randomUUID().toString(), mapping.getKey(), mapping.getLabel(), AuthorityType.Write);
	}

	private NodeAttribute buildNodeAttribute(Mapping mapping, List<NodeAttribute> dbNodeAttribute) {
		if (dbNodeAttribute != null) {
			for (NodeAttribute na : dbNodeAttribute) {
				if (na.getName().equals(mapping.getKey())) {
					return na;
				}
			}
		}
		return new NodeAttribute(UUID.randomUUID().toString(), mapping.getKey(), mapping.getLabel(), AuthorityType.Write);
	}

	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.authorizeAttributeColl = applicationContext.getBeansOfType(AuthorizeAttribute.class).values();
	}

	@SuppressWarnings("unchecked")
	public List<ProcessEntry> findDbProcessEntry(long processId) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(ProcessEntry.class);
		dc.add(Restrictions.eq("processId", processId));
		dc.addOrder(Order.desc("createDate"));
		return (List<ProcessEntry>) commandService.executeCommand(new QueryListCommand(dc));
	}

	@SuppressWarnings("unchecked")
	public List<ProcessEntryAssignee> findDbProcessEntryAssignee(String processEntryId) throws Exception {
		DetachedCriteria dc = DetachedCriteria.forClass(ProcessEntryAssignee.class);
		dc.add(Restrictions.eq("processEntryId", processEntryId));
		return (List<ProcessEntryAssignee>) commandService.executeCommand(new QueryListCommand(dc));
	}

	public void updateNodeEntry(String nodeEntryId, String nodeName) throws Exception {
		commandService.executeCommand(new UpdateNodeEntryCommand(nodeEntryId, nodeName));
	}

	public Collection<AuthorizeAttribute> getAuthorizeAttributes() throws Exception {
		return authorizeAttributeColl;
	}

}
